package com.zhangx.study.demo.config;

import lombok.extern.slf4j.Slf4j;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.recipes.cache.ChildData;
import org.apache.curator.framework.recipes.cache.PathChildrenCache;
import org.apache.curator.framework.recipes.cache.PathChildrenCacheEvent;
import org.apache.curator.framework.recipes.cache.PathChildrenCacheListener;
import org.apache.curator.retry.RetryUntilElapsed;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.data.Stat;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.env.OriginTrackedMapPropertySource;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.env.MutablePropertySources;
import org.springframework.core.env.PropertySource;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.nio.charset.Charset;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @ClassName ZkConfig
 * @Description zk配置
 * @Author zhangx
 * @Date 2021/4/12 8:28
 **/
@Slf4j
@Component
public class ZkConfig {

    @Value("${zookeeper.client}")
    private String connectStr;

    private static CuratorFramework zkClient;

    private static String path = "/config";

    @Value("${zookeeper.config.enable:false}")
    private boolean enable;
    /**
     * 用于获取envirment中的propertysources
     */
    @Autowired
    private ConfigurableApplicationContext configurableApplicationContext;
//    放在environment中的property 名字
    private static String zkPropertyName = "zkSource";

    /**
     *
     */
    @PostConstruct
    public void init(){
        if (!enable){
            log.info("未开启配置中心");
            return;
        }
        zkClient = CuratorFrameworkFactory.builder().
                connectString(connectStr).
                sessionTimeoutMs(5000).
                retryPolicy(new RetryUntilElapsed(1000,3000))
                .build();
        zkClient.start();

        try {
            Stat stat = zkClient.checkExists().forPath(path);
//            /config节点不存在就创建一个
            if (stat == null){
                zkClient.create().creatingParentsIfNeeded().withMode(CreateMode.PERSISTENT).
                        forPath(path,"zookeeper config".getBytes());
            }else{
//                将zk中的配置加载到spring中
                addChildToSpringProperty(zkClient,path);
            }
//            增加zk 监听，动态刷新
            childNodeCache(zkClient,path);
        } catch (Exception e) {
            e.printStackTrace();
        }

    }


    /**
     * 当zk 发生变化监听
     * @param zkClient
     * @param path
     */
    private void childNodeCache(CuratorFramework zkClient, String path) {
//        todo 还未填充逻辑
        PathChildrenCache pathChildrenCache = new PathChildrenCache(zkClient,path,true);
        try {
            pathChildrenCache.start(PathChildrenCache.StartMode.POST_INITIALIZED_EVENT);
        } catch (Exception e) {
            e.printStackTrace();
        }
        pathChildrenCache.getListenable().addListener(new PathChildrenCacheListener() {
            @Override
            public void childEvent(CuratorFramework curatorFramework, PathChildrenCacheEvent event) throws Exception {
                switch (event.getType()){
                    case CHILD_ADDED:
                        log.info("CHILD_ADDED");
                        addEnv(event.getData(),curatorFramework);
                        break;
                    case CHILD_UPDATED:
                        log.info("CHILD_UPDATED");
                        addEnv(event.getData(),curatorFramework);
                        break;
                    case CHILD_REMOVED:
                        log.info("CHILD_REMOVED");
                        delEnv(event.getData());
                        break;
                }
            }
        });
    }

    private void delEnv(ChildData data) {
        log.info("zk 中删除了节点 {}",new String(data.getData()));
        String key = data.getPath().substring(path.length()+1);
        MutablePropertySources propertySources = configurableApplicationContext.getEnvironment().getPropertySources();
        PropertySource ps = propertySources.get(zkPropertyName);
//        spring中 property实际就是CHM
        ConcurrentHashMap zkMap = (ConcurrentHashMap)ps.getSource();
        zkMap.remove(key);
    }


    /**
     * 将zk中增加的节点加入到spring
     * @param data
     * @param curatorFramework
     */
    private void addEnv(ChildData data, CuratorFramework curatorFramework) {
        log.info("zk 中增加了节点 {}",new String(data.getData()));
//        截取 /config之后的 数据是真正的key
        String key = data.getPath().substring(path.length()+1);
        String val = new String(data.getData());
        MutablePropertySources propertySources = configurableApplicationContext.getEnvironment().getPropertySources();
        PropertySource ps = propertySources.get(zkPropertyName);
//        spring中 property实际就是CHM
        ConcurrentHashMap<String,String> zkMap = (ConcurrentHashMap)ps.getSource();
        zkMap.put(key,val);

    }

    /**
     * 将从zk 中取到的配置加载到spring environment中
     * @param zkClient
     * @param path
     */
    private void addChildToSpringProperty(CuratorFramework zkClient, String path) {
        if (!checkExistsSpringProperty()){
//            如果spring 中不存在需要创建
            createZkSpringProperty();
        }
//      如果存在证明已经加载了，不需要做什么

//        后面开始读取zk 的节点放入spring 中
        MutablePropertySources propertySources = configurableApplicationContext.getEnvironment().getPropertySources();
        PropertySource ps = propertySources.get(zkPropertyName);
//        spring中 property实际就是CHM
        ConcurrentHashMap<String,String> zkMap = (ConcurrentHashMap)ps.getSource();

        try {
//            拿到/config 下面的子目录
            List<String> zkNodes = zkClient.getChildren().forPath(path);
            for (String node:zkNodes) {
//                真正的节点是 /config(path)/cn.username(node)
                String realPath = path + "/" + node;
                String data = new String(zkClient.getData().forPath(realPath));
                zkMap.put(node,data);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }


    }

    /**
     * 查看spring 中是否存在名称为zkSource的PropertySource
     * @return
     */
    private boolean checkExistsSpringProperty() {
        MutablePropertySources propertySources = configurableApplicationContext.getEnvironment().getPropertySources();
        for (PropertySource ps:propertySources) {
            if (zkPropertyName.equals(ps.getName())){
                return true;
            }
        }
        return false;
    }

    /**
     * 在spring 中创建名称为zkSource的PropertySource
     */
    private void createZkSpringProperty(){
        MutablePropertySources propertySources = configurableApplicationContext.getEnvironment().getPropertySources();
        OriginTrackedMapPropertySource zkSource = new OriginTrackedMapPropertySource(zkPropertyName,new ConcurrentHashMap());
        propertySources.addLast(zkSource);
    }
}
